package YAPserver;

import java.util.HashMap;
import java.util.concurrent.ConcurrentLinkedQueue;

import YAPenvironment.YAPconstant;
import YAPenvironment.YAPconstant.Command;
import YAPserver.logic.ClassicModel;
import YAPserver.logic.Game;

public class Lobby {

	private ConcurrentLinkedQueue<ClientHandler> m_playersInLobby= new ConcurrentLinkedQueue<ClientHandler>();
	private volatile boolean m_started=false;
	private String m_lobbyName;
	private int m_maxPlayer;
	private int m_numberOfWalls;
	private double m_radReg;
	private int m_lifes;
	private ClientHandler m_host;
	private Game YAP;
	private boolean[] sideTaken;
	private GameActualizer gameActualizer;
	
	private boolean m_isInCountdownMode = false;

	
	/**
	 * Constructs a lobby-object
	 * @param cHlobbyHost
	 * @param strLobbyName
	 * @param maxPlayer
	 */
	Lobby(ClientHandler cHlobbyHost, String strLobbyName, int maxPlayer){
		m_playersInLobby.add(cHlobbyHost);
		m_host=cHlobbyHost;
		m_lobbyName=strLobbyName;
		m_maxPlayer=maxPlayer;
		if(maxPlayer==2)
			m_numberOfWalls=4;
		else
			m_numberOfWalls=maxPlayer;
		m_radReg=0;
		m_lifes=YAPconstant.C_LIVES;
		
		YAP = new Game(m_playersInLobby, m_numberOfWalls, this);
		gameActualizer = new GameActualizer(m_playersInLobby, YAP, this);
		YAP.setOutput(gameActualizer);
		
		sideTaken=new boolean[m_numberOfWalls];
		sideTaken[0]=true;
		for(int i=1; i<sideTaken.length; i++)
			sideTaken[i]=false;
		
	}
	/**
	 * @return number of lives at the beginning of the game
	 */
	int getLifes(){
		return m_lifes;
	}
	/**
	 * @return the state of the game (started or not)
	 */
	boolean isStarted(){
		return m_started;
	}
	/**
	 * @return if there is more room for one more client
	 */
	boolean isFull(){
		return m_playersInLobby.size()>=m_maxPlayer;
	}
	/**
	 * @return the client who created the lobby
	 */
	ClientHandler getLobbyHost(){
		return m_host;
	}
	/**
	 * @return lobbyName
	 */
	String getName(){
		return m_lobbyName;
	}
//	int getSize(){
//		return m_playersInLobby.size();
//	}
//	int getMaxPlayers(){
//		return m_maxPlayer;
//	}
//	int getPlayerCount(){
//		return m_playersInLobby.size();
//	}
//	boolean inLobby(){
//		return true;
//	}
	/**
	 * Changes the playing field using the given parameters (if possible).
	 * @param walls
	 * @param radReg
	 * @param lives
	 * @param regular
	 */
	synchronized void changePlayField(int walls, double radReg, int lives, boolean regular){
		if(!m_started){
			setNumberOfWalls(walls);
			sideTaken=new boolean[m_numberOfWalls];
			arrangePlayers();
			setRadReg(radReg);
			setNumberOfLives(lives);
			YAP.setField(m_numberOfWalls, m_radReg, regular);
		}
	}
	
	/**
	 * Assigns a client to a slider/wall.
	 */
	private void arrangePlayers(){
		int i=0;
		for(ClientHandler cH: m_playersInLobby){
			cH.setGameId(i);
			sideTaken[i]=true;
			i++;
		}
	}
	/**
	 * Changes the number of walls (if possible).
	 * @param walls
	 */
	private void setNumberOfWalls(int walls){
		if(walls<3 || walls<m_maxPlayer)
			return;
		
		int i=0;
		sideTaken=new boolean[m_numberOfWalls];
		for(ClientHandler cH: m_playersInLobby){
			cH.setGameId(i);
			i++;
		}
			
		m_numberOfWalls=walls;
	}
	/**
	 * Changes the radial regularity (if possible and in range).
	 * @param reg
	 */
	private void setRadReg(double reg){
		if(reg>=0 && reg<=1)
			m_radReg=reg;
	}
	/**
	 * Changes the number of lives (if possible and in range).
	 * @param lives
	 */
	private void setNumberOfLives(int lives){
		if(lives >= 0 && lives <=YAPconstant.C_MAXLIVES){
			m_lifes=lives;
			YAP.setLives(lives);
		}	
	}
	/**
	 * Adds a player to the lobby.
	 * @param cH
	 * @return inGameId/slider/wall
	 */
	synchronized int addPlayer(ClientHandler cH){
		if(m_started)
			return -1;
		m_playersInLobby.add(cH);
		for(int i=0; i<sideTaken.length; i++)
			if(!sideTaken[i]){
				sideTaken[i]=true;
				return i;
			}
		return -9000;
	}
	/**
	 * Removes a player from the lobby.
	 * If the lobby is empty, it is closed.
	 * @param cH
	 */
	void removePlayer(ClientHandler cH){
		if(m_started){
			YAP.removePlayer(cH.getGameId());
		}

		sideTaken[cH.getGameId()]=false;
		m_playersInLobby.remove(cH);
		if(m_playersInLobby.isEmpty()) 
			closeLobby();
		else if(m_started && m_isInCountdownMode && m_playersInLobby.size()==1)
			endGame(m_playersInLobby.peek().getGameId());
	}
	/**
	 * Closes the lobby.
	 */
	void closeLobby(){
		if(m_started){
			m_started=false;
			gameActualizer.interrupt();
		}
		YAPserver.lobbyList.remove(m_lobbyName);
	}
	/**
	 * @return Lobby for the lobby-list as a string in form like in the protocol described
	 */
	public String toString(){
		return m_lobbyName+YAPconstant.C_SEPARATOR+m_playersInLobby.size()+YAPconstant.C_SEPARATOR+m_maxPlayer;
	}
	/**
	 * @return String for the ingameLobby with walls and sides taken etc.
	 */
	public synchronized String toLobbyInformationString(){
		String string="";
		for(ClientHandler cH: m_playersInLobby){
			string+=cH.getName()+YAPconstant.C_SEPARATOR+cH.isReady()+YAPconstant.C_SEPARATOR;
		}
		string+=YAPconstant.C_NEWLINE;
		
		string+=YAP.getField();
		
		string+=YAPconstant.C_NEWLINE;
		string+=m_maxPlayer+YAPconstant.C_SEPARATOR+m_numberOfWalls+YAPconstant.C_SEPARATOR+m_radReg;
		
		return string;
	}
	/**
	 * @return String of which sides are taken
	 */
	public synchronized String getSides(){
		String s="";
		for(int i=0; i<sideTaken.length; i++)
			s+=sideTaken[i]+YAPconstant.C_SEPARATOR;
		return s;
	}
	/**
	 * @param int of the old side
	 * @param int of the new side (which wants to be acquired)
	 * @return if the side is free and can be acquired
	 */
	public synchronized boolean pickSide(int alt, int neu){
		if(!sideTaken[neu]){
			sideTaken[neu]=true;
			sideTaken[alt]=false;
			return true;
		}
		return false;
	}
	/**
	 * @return the list of the players
	 */
	public ConcurrentLinkedQueue<ClientHandler> getPlayers(){
		return m_playersInLobby;
	}
	/**
	 * @return the instance of the game in the gameLogic
	 */
	public Game getGame(){
		return YAP;
	}
	/**
	 * Starts the game.
	 */
	void startGame(){
		if(m_started)
			return;
		System.out.println(m_lobbyName+": ***LET THE GAME BEGIN***");

		HashMap<Integer, Integer> playersSliders = new HashMap<Integer, Integer>();
		for(ClientHandler client: m_playersInLobby){
			playersSliders.put(client.getGameId(), client.getGameId());
		}
		YAP.setPlayers(m_playersInLobby);
		YAP.setGameModel(new ClassicModel(), playersSliders);
		
		addAsPlayed();
		m_started=true;
		m_isInCountdownMode = true;

		
		
		//Says every Player in Lobby: game is about to start
		new gameStarter().start();
	}
	/**
	 * Adds the lobby as played on the server.
	 */
	private void addAsPlayed(){
		String[] names = new String[m_playersInLobby.size()];
		int i=0;
		for(ClientHandler cH: m_playersInLobby){
			names[i]=cH.getName();
			i++;
		}
		PlayedGames.addGame(m_lobbyName,names);
	}
	/**
	 * Pauses or unpauses the game.
	 */
	void pauseGame(){
		if(YAP != null && m_started){
			YAP.pause();
		}
	}
	
	boolean isPaused(){
		if(YAP !=null)
			return YAP.isPaused();
		return false;
	}
	/**
	 * Ends the game after it has finished
	 * and increases the high score of the winning player.
	 * @param id of the winning players
	 */
	public void endGame(int id){
		m_started=false;
		this.getLobbyHost().pushLobbyList(); //quick fix mögl. 1 
		String winner="";
		String awardWinners="";
		for(ClientHandler cH: m_playersInLobby){
			if(id==cH.getGameId()){
				//cH.pushLobbyList(); quick fix mögl. 2
				HighScore.increaseHighScore(cH.getName());
				String score = YAP.getScore();
				if(score!=null){
					awardWinners=score + cH.getName();
					winner = cH.getName();
				}
				else
					winner=cH.getName();
				break;
			}}
		
		for(ClientHandler cH: m_playersInLobby){
			try{
				cH.writeMessage(Command.endGame+YAPconstant.C_SEPARATOR+awardWinners);
				cH.sendLobbyInformation(m_lobbyName);
			}catch (Exception e){
				if(YAPconstant.C_DEBUG){ e.printStackTrace(); }
			}
		}
		
		
		/*
		 * Set Players in Lobby Un-Ready
		 * Set inGameId to i;
		 */
		
		YAP = new Game(m_playersInLobby, m_numberOfWalls, this);
		YAP.setLives(m_lifes);
		YAP.setField(m_numberOfWalls, m_radReg, false);
		gameActualizer = new GameActualizer(m_playersInLobby, YAP, this);
		YAP.setOutput(gameActualizer);
		
		sideTaken=new boolean[m_numberOfWalls];
		sideTaken[0]=true;
		for(int i=0; i<sideTaken.length; i++)
			sideTaken[i]=false;
		int i=0;
		
		for(ClientHandler cH: m_playersInLobby){
			//LobbyInformation senden
			cH.setGameId(i);
			sideTaken[i]=true;
			try{
				cH.setUnready();
			}catch (Exception e){
				//ignore
			}
			i++;
		}
		System.out.println(m_lobbyName+": ***ENDED*** " + winner + " won\n");
	}
	/**
	 * A private class which counts from 3 to 0 and starts the game.
	 * @author DMiagi
	 *
	 */
	private class gameStarter extends Thread{

		public void run() {
			for(int i=3; i>=1; i--){
				
				for(ClientHandler cH: m_playersInLobby){
					try{
						cH.writeMessage(Command.startGame.toString()+YAPconstant.C_SEPARATOR.toString()+i+YAPconstant.C_CMD_SEPARATOR.toString());
					}catch (Exception e){
						if(YAPconstant.C_DEBUG){ e.printStackTrace(); }
					}
				}
				
				try{
					Thread.sleep(YAPconstant.C_COUNTDOWNTIME);
				}catch (InterruptedException e){
					//ignore
				}
				if(!m_started)
					break;
			}
			
			if(m_started){
				for(ClientHandler cH: m_playersInLobby){
					try{
						cH.writeMessage(Command.startGame.toString()+YAPconstant.C_SEPARATOR.toString()+"0"+YAPconstant.C_CMD_SEPARATOR.toString());
					}catch (Exception e){
						if(YAPconstant.C_DEBUG){ e.printStackTrace(); }
					}
				}
				
				YAP.setOutput(gameActualizer);
				YAP.start();
				m_isInCountdownMode = false;
				gameActualizer.start();
			}
		}
	}
}
